﻿/*
    PerstSyncProvider
    Copyright (C) 2011 Mikolas Bingemer, MediFox GmbH

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    MA 02110-1301  USA.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Synchronization.ClientServices;
using Perst;

namespace MediFox.Synchronization.ClientServices.PerstSyncProvider
{
    [SerializeProperties]
    public abstract class PerstOfflineEntity : Persistent, IOfflineEntity, INotifyPropertyChanged, IDisposable
    {
        private Database _database;
        private volatile bool _isDirty;

        private Dictionary<string, bool> _initializedProperties = new Dictionary<string, bool>();

        #region properties

        [NonSerialized]
        internal Database Database
        {
            get
            {
                return _database;
            }
            set
            {
                _database = value;
            }
        }

        [Indexable(Thick = true)]
        public bool IsDirty
        {
            get
            {
                return _isDirty;
            }
            set
            {
                //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod(), string.Format("{0} {1} {2} {3}", this.GetType().Name, this.Oid, this._isDirty, value));

                bool isInitialized = _initializedProperties.ContainsKey("IsDirty");
                if (!isInitialized)
                {
                    // mark this property as initialized
                    _initializedProperties.Add("IsDirty", true);
                }

                if (_isDirty != value)
                {
                    if (isInitialized && IsPersistent())
                    {
                        // remove from index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.ExcludeFromIndex(typeof(PerstOfflineEntity), this, "IsDirty");
                    }

                    // set dirty for sync flag
                    _isDirty = value;

                    if (isInitialized)
                    {
                        // mark record as modified for persistence
                        Modify();

                        if (IsPersistent())
                        {
                            // re-insert into index
                            System.Diagnostics.Debug.Assert(_database != null);
                            if (_database != null)
                                _database.IncludeInIndex(typeof(PerstOfflineEntity), this, "IsDirty");
                        }
                    }

                    // notify
                    RaisePropertyChanged("IsDirty");
                }
            }
        }

        #region IOfflineEntity

        private OfflineEntityMetadata _entityMetadata;

        [NonSerialized]
        public OfflineEntityMetadata ServiceMetadata
        {
            get
            {
                if (_entityMetadata == null)
                {
                    _entityMetadata = new OfflineEntityMetadata();
                    _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);
                }
                return _entityMetadata;
            }
            set
            {
                if (_entityMetadata != value)
                {
                    //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod());

                    if (_entityMetadata != null)
                    {
                        _entityMetadata.PropertyChanged -= Metadata_PropertyChanged;
                    }

                    if (_initializedProperties.ContainsKey("MetadataId") && IsPersistent())
                    {
                        // remove from index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.ExcludeFromIndex(typeof(PerstOfflineEntity), this, "MetadataId");
                    }
                    if (_initializedProperties.ContainsKey("MetadataIsTombstone") && IsPersistent())
                    {
                        // remove from index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.ExcludeFromIndex(typeof(PerstOfflineEntity), this, "MetadataIsTombstone");
                    }

                    _entityMetadata = value;
                    _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);

                    if (_entityMetadata != null && IsPersistent())
                    {
                        // re-insert into index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                        {
                            _database.IncludeInIndex(typeof(PerstOfflineEntity), this, "MetadataId");
                            _database.IncludeInIndex(typeof(PerstOfflineEntity), this, "MetadataIsTombstone");
                        }
                    }

                    // mark record as modified for persistence
                    Modify();

                    if (!_initializedProperties.ContainsKey("MetadataEditUri"))
                    {
                        _initializedProperties.Add("MetadataEditUri", true);
                    }
                    if (!_initializedProperties.ContainsKey("MetadataETag"))
                    {
                        _initializedProperties.Add("MetadataETag", true);
                    }
                    if (!_initializedProperties.ContainsKey("MetadataId"))
                    {
                        _initializedProperties.Add("MetadataId", true);
                    }
                    if (!_initializedProperties.ContainsKey("MetadataIsTombstone"))
                    {
                        _initializedProperties.Add("MetadataIsTombstone", true);
                    }

                    // notify
                    RaisePropertyChanged("ServiceMetadata");
                }
            }
        }

        public string MetadataEditUri
        {
            get
            {
                return (_entityMetadata != null && _entityMetadata.EditUri != null) ? _entityMetadata.EditUri.ToString() : null;
            }
            set
            {
                //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod()); 

                if (_entityMetadata == null || _entityMetadata.EditUri != null && _entityMetadata.EditUri.AbsoluteUri != value)
                {

                    Uri uri = null;
                    if (!string.IsNullOrEmpty(value))
                    {
                        if (!Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out uri))
                        {
                            throw new ArgumentException("Value is no valid Uri", "MetadataEditUri");
                        }
                    }

                    if (_entityMetadata == null)
                    {
                        _entityMetadata = new OfflineEntityMetadata();
                        _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);
                    }

                    _entityMetadata.EditUri = uri;
                }
            }
        }

        public string MetadataETag
        {
            get
            {
                return (_entityMetadata != null) ? _entityMetadata.ETag : null;
            }
            set
            {
                if (_entityMetadata == null || _entityMetadata.ETag != value)
                {
                    //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod());

                    if (_entityMetadata == null)
                    {
                        _entityMetadata = new OfflineEntityMetadata();
                        _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);
                    }

                    _entityMetadata.ETag = value;
                }
            }
        }

        [Indexable]
        public string MetadataId
        {
            get
            {
                return (_entityMetadata != null) ? _entityMetadata.Id : null;
            }
            set
            {
                if (_entityMetadata == null || _entityMetadata.Id != value)
                {
                    //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod());

                    if (_entityMetadata == null)
                    {
                        _entityMetadata = new OfflineEntityMetadata();
                        _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);
                    }

                    bool isInitialized = _initializedProperties.ContainsKey("MetadataId");
                    if (isInitialized && IsPersistent())
                    {
                        // remove from index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.ExcludeFromIndex(typeof(PerstOfflineEntity), this, "MetadataId");
                    }

                    _entityMetadata.Id = value;

                    if (isInitialized && IsPersistent())
                    {
                        // re-insert into index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.IncludeInIndex(typeof(PerstOfflineEntity), this, "MetadataId");
                    }
                }
            }
        }

        [Indexable(Thick = true)]
        public bool MetadataIsTombstone
        {
            get
            {
                return (_entityMetadata != null) ? _entityMetadata.IsTombstone : false;
            }
            set
            {
                if (_entityMetadata == null || _entityMetadata.IsTombstone != value)
                {
                    //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod());

                    if (_entityMetadata == null)
                    {
                        _entityMetadata = new OfflineEntityMetadata();
                        _entityMetadata.PropertyChanged += new PropertyChangedEventHandler(Metadata_PropertyChanged);
                    }

                    bool isInitialized = _initializedProperties.ContainsKey("MetadataIsTombstone");
                    if (isInitialized && IsPersistent())
                    {
                        // remove from index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.ExcludeFromIndex(typeof(PerstOfflineEntity), this, "MetadataIsTombstone");
                    }

                    _entityMetadata.IsTombstone = value;

                    if (isInitialized && IsPersistent())
                    {
                        // re-insert into index
                        System.Diagnostics.Debug.Assert(_database != null);
                        if (_database != null)
                            _database.IncludeInIndex(typeof(PerstOfflineEntity), this, "MetadataIsTombstone");
                    }
                }
            }
        }

        #endregion

        #endregion

        private void Metadata_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod(), e.PropertyName);

            string propertyName = string.Format("Metadata{0}", e.PropertyName);

            if (_initializedProperties.ContainsKey(propertyName))
            {
                // mark record as modified
                Modify();
            }
            else
            {
                // mark this property as initialized
                _initializedProperties.Add(propertyName, true);
            }

            // notify
            RaisePropertyChanged(propertyName);
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanging(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentException("propertyName");
            }

            //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod(), propertyName);

            if (_initializedProperties.ContainsKey(propertyName) && IsPersistent())
            {
                // remove from index (if property is indexed)
                System.Diagnostics.Debug.Assert(_database != null);
                if (_database != null)
                    _database.ExcludeFromIndex(this, propertyName);
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentException("propertyName");
            }

            //Logger.Log(System.Reflection.MethodBase.GetCurrentMethod(), propertyName);

            if (_initializedProperties.ContainsKey(propertyName))
            {
                if (IsPersistent())
                {
                    // re-insert into index (if property is indexed)
                    System.Diagnostics.Debug.Assert(_database != null);
                    if (_database != null)
                        _database.IncludeInIndex(this, propertyName);
                }

                // mark record as dirty for sync
                IsDirty = true;

                // mark record as modified for persistence
                Modify();
            }
            else
            {
                // mark this property as initialized
                _initializedProperties.Add(propertyName, true);
            }

            // notify
            RaisePropertyChanged(propertyName);
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            if (_entityMetadata != null)
            {
                _entityMetadata.PropertyChanged -= Metadata_PropertyChanged;
            }
            _database = null;
        }

        #endregion

        public override void Deallocate()
        {
            base.Deallocate();
            _database = null;
        }

    }
}
